﻿
Imports System.Threading
Imports System.Timers
Imports osi.root.formation
Imports osi.root.delegates
Imports osi.root.connector
Imports osi.root.lock
Imports osi.root.envs
Imports osi.root.constants

Namespace stopwatch
    Public Module _stopwatch
        Private ReadOnly DELAY As Int64 = 0

        Public Class stopwatch_event
            Private Const IS_CANCELED As Int32 = 1
            Private Const IS_NOT_CANCELED As Int32 = 0
            Private _canceled As Int32 = IS_NOT_CANCELED

            Private Const IS_NOT_STARTED As Int32 = 0
            Private Const IS_BEFORE_CALLBACK As Int32 = 1
            Private Const IS_AFTER_CALLBACK As Int32 = 2
            Private _step As Int32 = IS_NOT_STARTED

            Private ReadOnly waitms As UInt32 = 0
            Private ReadOnly d As Action = Nothing
            Private lastms As Int64 = 0

            Public Sub New(ByVal waitms As UInt32, ByVal d As Action)
                assert(Not d Is Nothing)
                Me.d = d
                Me.waitms = waitms
                Me.lastms = nowadays.milliseconds()
            End Sub

            'return true if not runned but need to run next time
            'return false if do not need to run next time
            Friend Function [do]() As Boolean
                If canceled() Then
                    Return False
                Else
                    current() = Me
                    Try
                        Dim diff As Int64 = 0
                        diff = nowadays.milliseconds() - lastms - waitms
                        If diff >= 0 Then
                            counter.increase(DELAY, diff)
                            lastms = nowadays.milliseconds()
                            mark_before_callback()
                            void_(d)
                            mark_after_callback()
                            Return False
                        Else
                            Return True
                        End If
                    Finally
                        current() = Nothing
                    End Try
                End If
            End Function

            Public Shared Property current() As stopwatch_event
                Get
                    Return call_stack(Of stopwatch_event).current()
                End Get
                Private Set(ByVal value As stopwatch_event)
                    call_stack(Of stopwatch_event).current() = value
                End Set
            End Property

            Private Sub mark_before_callback()
                If isdebugmode() Then
                    assert(atomic.compare_exchange(_step, IS_BEFORE_CALLBACK, IS_NOT_STARTED))
                Else
                    atomic.eva(_step, IS_BEFORE_CALLBACK)
                End If
            End Sub

            Private Sub mark_after_callback()
                If isdebugmode() Then
                    assert(atomic.compare_exchange(_step, IS_AFTER_CALLBACK, IS_BEFORE_CALLBACK))
                Else
                    atomic.eva(_step, IS_AFTER_CALLBACK)
                End If
            End Sub

            Public Sub restart()
                If isdebugmode() Then
                    assert(atomic.compare_exchange(_step, IS_NOT_STARTED, IS_AFTER_CALLBACK))
                Else
                    atomic.eva(_step, IS_NOT_STARTED)
                End If
            End Sub

            Public Sub cancel()
                If isdebugmode() Then
                    atomic.compare_exchange(_canceled, IS_CANCELED, IS_NOT_CANCELED)
                Else
                    atomic.eva(_canceled, IS_CANCELED)
                End If
            End Sub

            Public Function [resume]() As Boolean
                If isdebugmode() Then
                    Return atomic.compare_exchange(_canceled, IS_NOT_CANCELED, IS_CANCELED)
                Else
                    atomic.eva(_canceled, IS_NOT_CANCELED)
                    Return True
                End If
            End Function

            Public Function canceled() As Boolean
                Return atomic.read(_canceled) = IS_CANCELED
            End Function

            Private Function [step]() As Int32
                Return atomic.read(_step)
            End Function

            Public Function not_started() As Boolean
                Return [step]() = IS_NOT_STARTED
            End Function

            Public Function started() As Boolean
                Return [step]() <> IS_NOT_STARTED
            End Function

            Public Function before_callback() As Boolean
                Return [step]() = IS_BEFORE_CALLBACK
            End Function

            Public Function after_callback() As Boolean
                Return [step]() = IS_AFTER_CALLBACK
            End Function
        End Class

        Sub New()
            DELAY = counter.register_average_and_last_average("STOPWATCH_DELAY_MS")
        End Sub

        Public Function push(ByVal e As stopwatch_event) As Boolean
            If e Is Nothing OrElse e.canceled() Then
                Return False
            Else
                Return queue_runner.push(AddressOf e.do)
            End If
        End Function

        Public Function push(ByVal waitms As UInt32, ByVal d As Action) As stopwatch_event
            If d Is Nothing Then
                Return Nothing
            Else
                Dim rtn As stopwatch_event = Nothing
                rtn = New stopwatch_event(waitms, d)
                assert(push(rtn))
                Return rtn
            End If
        End Function

        'the waitms is not consistant with other timeout settings,
        'since it's not reasonable to have a stopwatch event to be run in max_uint32 milliseconds later
        Friend Function _uint32(ByVal i As Int32) As UInt32
            Return If(i < 0, 0, i)
        End Function

        Friend Function _uint32(ByVal i As Int64) As UInt32
            Return If(i < 0, 0, If(i > max_uint32, max_uint32, i))
        End Function

        Public Function push(ByVal waitms As Int64, ByVal d As Action) As stopwatch_event
            Return push(_uint32(waitms), d)
        End Function

        Public Function push(ByVal waitms As Int32, ByVal d As Action) As stopwatch_event
            Return push(_uint32(waitms), d)
        End Function
    End Module
End Namespace
